package org.wattdepot.simulation.uhcampus.building;

import java.util.Random;
import javax.xml.datatype.XMLGregorianCalendar;
import org.wattdepot.client.BadXmlException;
import org.wattdepot.client.MiscClientException;
import org.wattdepot.client.NotAuthorizedException;
import org.wattdepot.client.ResourceNotFoundException;
import org.wattdepot.client.WattDepotClient;
import org.wattdepot.resource.property.jaxb.Property;
import org.wattdepot.resource.sensordata.jaxb.SensorData;
import org.wattdepot.resource.source.jaxb.Source;
import org.wattdepot.util.tstamp.Tstamp;

/**
 * Implements the set of building loads (WattDepot sources) used in the UH Campus simulation.
 * 
 * Each instance is initialized with the yearly kWh consumed from a recent year's data. 
 * To model the amount consumed during the day, we first divide the yearly value by 365 to
 * get the average kWh per day.  We do not consider weekends, summer, Manoa Green Days,
 * or similar in this model.
 * <p>
 * We next divide the day into the three classes:
 * <ul>
 * <li> Night (12am-7am, 6pm-12am): Energy consumed each hour is 2.3% of average kWh/day.
 * <li> Transition (7am-8am, 5pm-6pm): Energy consumed each hour is 4.0% of average kWh/day. 
 * <li> Day (8am - 5pm): Energy consumed each hour is 6.8% of average kWh/day.
 * </ul>  
 * <p>
 * We introduce random jiggle by picking a random number between 0 and the average
 * kWh per hour and adding that onto the values computed above. 
 * 
 * @author Philip Johnson
 *
 */
public enum BuildingLoad {
  
  /** Post Building. */
  SIM_UH_POST          (9192000),
  /** Holmes Hall. */
  SIM_UH_HOLMES_LOAD   (5558000),
  /** Saunders Hall. */
  SIM_UH_SAUNDERS_LOAD (4760000),
  /** Shidler. */
  SIM_UH_BUSAD_LOAD    (3118080),
  /** Watanabe Hall. */
  SIM_UH_WATANABE_LOAD (3075840),
  /** Keller Hall. */
  SIM_UH_KELLER        (2070360),
  /** Architecture Building. */
  SIM_UH_ARCHITECTURE  (1345920),
  /** Sinclair Library. */
  SIM_UH_SINCLAIR       (940800);
  
  
  /** Stores the meters internal counter. */
  private double energyConsumedToDate = 0;

  /** The typical kWh per hour consumed during night time hours. */
  private double baseNightLoad = 0;

  /** The typical kWh per hour consumed during transition time hours. */
  private double baseTransitionLoad = 0;

  /** The typical kWh per hour consumed during day time hours. */
  private double baseDayLoad = 0;
  
  /** A random number generator. */
  private final Random random = new Random(0);

  /** The maximum random variation, initialized to the average load during a 10 min interval. */
  private int maxJiggle = 0;
  
  /** Specifies the power consumed by the building during the last 20 minutes. */
  private double currentPower = 0;
  

  /**
   * Create and initialize this building's source for energy consumed.
   * @param kwHoursPerYear The yearly amount of kilowatt-hours consumed.
   */
  private BuildingLoad(double kwHoursPerYear) {
    double averageKwHoursPerDay = kwHoursPerYear / 365.0;
    this.baseNightLoad = averageKwHoursPerDay * 0.023;
    this.baseTransitionLoad = averageKwHoursPerDay * 0.040;
    this.baseDayLoad = averageKwHoursPerDay * 0.069;
    this.maxJiggle = (int)averageKwHoursPerDay / 144;
  }
  
  /**
   * Returns the base kWh per hour consumed by this Building given the current interval.
   * @param interval Represents a 20 minute interval through a number between 0 and 71.
   * @return The kWh per hour consumed during this interval.
   */
  private double getBaseLoad(int interval) {
    // If before 7am or after 6pm
    if ((interval < 21) || (interval > 48)) {
      return this.baseNightLoad;
    }
    // If between 8am and 5pm
    else if ((interval >= 24) && (interval <= 51)) {
      return this.baseDayLoad;
    }
    // Otherwise it's transition (7-8am or 5-6pm).
    else {
      return this.baseTransitionLoad;
    }
  }
  
  /**
   * Returns a random number between -maxJiggle and +maxJiggle.
   * @return The actual jiggle.
   */
  private double getJiggle() {
    int num = random.nextInt(maxJiggle);
    return (random.nextBoolean() ? num : (num * -1));
  }
  
  /**
   * Determine the energy consumed for this client.
   * @param client The WattDepotClient.
   */
  public void initializeEnergyConsumed(WattDepotClient client) {
    // First, validate the source. 
    try {
      client.getSource(name());
    }
    catch (NotAuthorizedException e) {
      System.out.format("You do not have access to the source %s. Aborting.%n", name());
      throw new RuntimeException(e);
    }
    catch (ResourceNotFoundException e) {
      System.err.format("Source %s does not exist on server. Aborting.%n", name());
      throw new RuntimeException(e);
    }
    catch (BadXmlException e) {
      System.err.println("Received bad XML from server, which is weird. Aborting.");
      throw new RuntimeException(e);
    }
    catch (MiscClientException e) {
      System.err.println("Had problems retrieving source from server, which is weird. Aborting.");
      throw new RuntimeException(e);
    }
    // Now, initialize its energy generated value. 
    try {
      this.energyConsumedToDate = client.getLatestEnergyConsumedToDate(name());
    }
    catch (ResourceNotFoundException e) {
      System.out.format("No prior energy consumption found for source %s%n", name());
    }
    catch (Exception e) {
      System.err.format("Had problems retrieving energy to date for source %s.%n", name());
      throw new RuntimeException(e);
    }
    System.out.format("%25s energy consumed to date:  %12.2f kWh%n", name(), 
        (this.energyConsumedToDate / 1000));
  }

  /**
   * Sends data about the current energy consumed and energy consumed to date to WattDepot.
   * @param client The WattDepotClient.
   */
  public void sendData(WattDepotClient client) {
    XMLGregorianCalendar timestamp = Tstamp.makeTimestamp();
    String toolName = "UHCampusSimulation";
    String sourceURI = Source.sourceToUri(name(), client.getWattDepotUri());
    SensorData data = new SensorData(timestamp, toolName, sourceURI);
    data.addProperty(new Property(SensorData.POWER_CONSUMED, currentPower));
    data.addProperty(new Property(SensorData.ENERGY_CONSUMED_TO_DATE, energyConsumedToDate));
    try {
      client.storeSensorData(data);
      System.out.format("%s %25s %10.2f kW %6.2f kWh%n", timestamp, name(), 
          (currentPower / 1000), (energyConsumedToDate / 1000));

    }
    catch (Exception e) {
      System.out.format("Error while sending data to WattDepot: %n  %s" + e.getMessage());
    }
  }

  /**
   * Updates this building's current power and energyConsumedToDate given this interval.
   * Assumes that this update method will be called every 20 minutes. 
   * @param interval The interval of this day.
   */
  public void update(int interval) {
    // Multiply by 1000 to convert from kilowatts to watts. 
    this.currentPower = 1000 * (this.getBaseLoad(interval) + this.getJiggle());
    this.energyConsumedToDate += this.currentPower / 3;
  }
}
